package it.uniroma2.editor;

import it.uniroma2.editor.extension.CustomGraphComponent;
import it.uniroma2.editor.extension.ServiceMultiplicity;
import it.uniroma2.orchestrator.Orchestrator;
import it.uniroma2.structure.LinkServiceNode;
import it.uniroma2.structure.Main_Sequence_Control;
import it.uniroma2.structure.Process;
import it.uniroma2.structure.Service;

import java.awt.Color;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.UIManager;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.mxgraph.examples.swing.GraphEditor;
import com.mxgraph.examples.swing.editor.BasicGraphEditor;
import com.mxgraph.examples.swing.editor.EditorMenuBar;
import com.mxgraph.examples.swing.editor.EditorPalette;
import com.mxgraph.io.mxCodec;
import com.mxgraph.io.mxCodecRegistry;
import com.mxgraph.io.mxObjectCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxICell;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxGraphTransferable;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxMultiplicity;

public class WorkflowEditor extends BasicGraphEditor
{
	/**
	 * 
	 */
	private static final long serialVersionUID = -4601740824088314699L;

	/**
	 * Holds the shared number formatter.
	 * 
	 * @see NumberFormat#getInstance()
	 */
	public static final NumberFormat numberFormat = NumberFormat.getInstance();
	
	private static EditorPalette shapesPalette;
	
	
//	private Vector<Nodo> totalNodes;

	private Process workflow;
	private Vector<Service> totalServizi;
	private Vector<Element> totalNodesXML;
	private int lastY = 30;
	private EditorPalette inPalette = null;
	private EditorPalette outPalette = null;
	private mxGraph graph;
	
	private mxCell startCell;
	private mxCell endCell;
	
	private Orchestrator orchestrator;
	

	public WorkflowEditor()
	{
		this("Workflow Editor", new CustomGraphComponent(new CustomGraph()));
		
	}
	
	
	/**
	 * 
	 */
	public WorkflowEditor(String appTitle, mxGraphComponent component)
	{
		super(appTitle, component);
		
		mxCodecRegistry.addPackage("it.uniroma2.structure");
		mxCodecRegistry.register(new mxObjectCodec(
				new it.uniroma2.structure.Service()));
		
		mxCodecRegistry.addPackage("it.uniroma2.editor");
		mxCodecRegistry.register(new mxObjectCodec(
				new it.uniroma2.editor.EndNode()));

		mxCodecRegistry.addPackage("it.uniroma2.editor");
		mxCodecRegistry.register(new mxObjectCodec(
				new it.uniroma2.editor.StartNode()));
		
		File file = new File("provaserviziclass.owl");
		this.orchestrator = new Orchestrator("file:" + file.getAbsolutePath());
		
		// Creates the shapes palette
		shapesPalette = insertPalette("Service");
		
		graph = graphComponent.getGraph();

		// Sets the edge template to be used for creating new edges if an edge
		// is clicked in the shape palette
		shapesPalette.addListener(mxEvent.SELECT, new mxIEventListener(){
			public void invoke(Object sender, mxEventObject evt)
			{
				Object tmp = evt.getProperty("transferable");

				if (tmp instanceof mxGraphTransferable)
				{
					mxGraphTransferable t = (mxGraphTransferable) tmp;
					Object cell = t.getCells()[0];

					if (graph.getModel().isEdge(cell))
					{
						((CustomGraph) graph).setEdgeTemplate(cell);
					}
				}
			}
		});
		
		getGraphComponent().getGraph().setCellsResizable(true);
		getGraphComponent().setConnectable(true);
		getGraphComponent().getGraphHandler().setCloneEnabled(false);
		
		getGraphComponent().getGraphControl().addMouseListener(new MouseListener() {
			@Override
			public void mouseReleased(MouseEvent e) {
				
			}
			
			@Override
			public void mousePressed(MouseEvent e) {
				
			}
			
			@Override
			public void mouseExited(MouseEvent e) {
				
			}
			
			@Override
			public void mouseEntered(MouseEvent e) {
				
			}
			
			@Override
			public void mouseClicked(MouseEvent e) {
				if(e.getSource()!=null){
					mxCell cell = ((mxCell)graphComponent.getCellAt(e.getX(), e.getY()));
					if(cell!=null && !cell.isEdge()){
//						System.out.println(cell.getValue());
						if(!cell.getValue().equals("Edge")){
//							System.out.println("Elements size : " + elements.size());
						}
					}
//					else{
//						createPanel(totalNodesXML);
//						updatePanel(totalNodes);
//					}
				}
			}
		});
		
		
		
		graph.setMultigraph(false);
		graph.setAllowDanglingEdges(false);
		graphComponent.setConnectable(true);
		graphComponent.setToolTips(true);
//		graph.getModel().addListener(mxEvent.CHANGE, new mxIEventListener()
//		{
//			public void invoke(Object sender, mxEventObject evt)
//			{
//				graphComponent.validateGraph();
//			}
//		});

		//Inizializza validazione automatica
		graphComponent.validateGraph();

	}

	
	private static void createPanel(Vector<Element> nodes) {
		shapesPalette.removeAll();
		shapesPalette.repaint();
//		System.out.println(nodes.size());
		if(nodes!=null){
			for(Element temp : nodes){
	//			System.out.println(temp.getNodeName());
				mxCell tableTemplate = new mxCell(temp, new mxGeometry(0, 0, 120+temp.getTagName().length()*3, 40), null);
				tableTemplate.setVertex(true);
				if(temp.getTagName().substring(0, 1).equalsIgnoreCase("b")){
					shapesPalette.addTemplate(
							temp.getTagName(),
							new ImageIcon(
									GraphEditor.class
											.getResource("/com/mxgraph/examples/swing/images/source-f-icon.png")), tableTemplate);
				}else if(temp.getTagName().substring(0, 2).equalsIgnoreCase("sv")){
					shapesPalette.addTemplate(
							temp.getTagName(),
							new ImageIcon(
									GraphEditor.class
											.getResource("/com/mxgraph/examples/swing/images/icon-c.gif")), tableTemplate);
				}else if(temp.getTagName().substring(0, 1).equalsIgnoreCase("t")){
					shapesPalette.addTemplate(
							temp.getTagName(),
							new ImageIcon(
									GraphEditor.class
											.getResource("/com/mxgraph/examples/swing/images/NLP_icon.jpg")), tableTemplate);
				}else{
					shapesPalette.addTemplate(
							temp.getTagName(),
							new ImageIcon(
									GraphEditor.class
											.getResource("/com/mxgraph/examples/swing/images/rectangle.png")), tableTemplate);
				}
			}
		}
	}
	
	
	private void updatePanel(Vector<Element> inService, Vector<Element> outService) {
		if(inPalette == null){
			inPalette = insertPalette("In Service");
		}else{
			inPalette.removeAll();
			inPalette.repaint();
		}
		if(outPalette == null){
			outPalette = insertPalette("Out Service");
		}else{
			outPalette.removeAll();
			outPalette.repaint();
		}
		

		for(Element temp : inService){
//			System.out.println(temp.getNodeName());
			mxCell tableTemplate = new mxCell(temp, new mxGeometry(0, 0, 120+temp.getTagName().length()*3, 40), null);
			tableTemplate.setVertex(true);
			if(temp.getTagName().substring(0, 1).equalsIgnoreCase("b")){
				inPalette.addTemplate(
						temp.getTagName(),
						new ImageIcon(
								GraphEditor.class
										.getResource("/com/mxgraph/examples/swing/images/source-f-icon.png")), tableTemplate);
			}else if(temp.getTagName().substring(0, 2).equalsIgnoreCase("sv")){
				inPalette.addTemplate(
						temp.getTagName(),
						new ImageIcon(
								GraphEditor.class
										.getResource("/com/mxgraph/examples/swing/images/icon-c.gif")), tableTemplate);
			}else if(temp.getTagName().substring(0, 1).equalsIgnoreCase("t")){
				inPalette.addTemplate(
						temp.getTagName(),
						new ImageIcon(
								GraphEditor.class
										.getResource("/com/mxgraph/examples/swing/images/NLP_icon.jpg")), tableTemplate);
			}else{
				inPalette.addTemplate(
						temp.getTagName(),
						new ImageIcon(
								GraphEditor.class
										.getResource("/com/mxgraph/examples/swing/images/rectangle.png")), tableTemplate);
			}
		}
		
		for(Element temp : outService){
//			System.out.println(temp.getNodeName());
			mxCell tableTemplate = new mxCell(temp, new mxGeometry(0, 0, 120+temp.getTagName().length()*3, 40), null);
			tableTemplate.setVertex(true);
//			outPalette.addTemplate(
//					temp.getTagName(),
//					new ImageIcon(
//							GraphEditor.class
//									.getResource("/com/mxgraph/examples/swing/images/rectangle.png")), tableTemplate);
			if(temp.getTagName().substring(0, 1).equalsIgnoreCase("b")){
				outPalette.addTemplate(
						temp.getTagName(),
						new ImageIcon(
								GraphEditor.class
										.getResource("/com/mxgraph/examples/swing/images/source-f-icon.png")), tableTemplate);
			}else if(temp.getTagName().substring(0, 2).equalsIgnoreCase("sv")){
				outPalette.addTemplate(
						temp.getTagName(),
						new ImageIcon(
								GraphEditor.class
										.getResource("/com/mxgraph/examples/swing/images/icon-c.gif")), tableTemplate);
			}else if(temp.getTagName().substring(0, 1).equalsIgnoreCase("t")){
				outPalette.addTemplate(
						temp.getTagName(),
						new ImageIcon(
								GraphEditor.class
										.getResource("/com/mxgraph/examples/swing/images/NLP_icon.jpg")), tableTemplate);
			}else{
				outPalette.addTemplate(
						temp.getTagName(),
						new ImageIcon(
								GraphEditor.class
										.getResource("/com/mxgraph/examples/swing/images/rectangle.png")), tableTemplate);
			}
		}
	}
	
	
	public mxGraph viewWorkflow() {
		Main_Sequence_Control root = (Main_Sequence_Control) this.workflow.getMainNode();
		LinkServiceNode inLink = root.getReceive().getFirstLink();
		Service firstService = (Service) inLink.getToNode();
		StartNode start = new StartNode();

		Object vertex = addNodes_Edge(start, firstService, inLink, graph);
		Vector<ServiceMultiplicity> dependency = new Vector<ServiceMultiplicity>();
		addMultiplicity(firstService, dependency);
		addNodes(firstService, graph, vertex, dependency);
		System.out.println(dependency.size());
		ServiceMultiplicity[] multi = new ServiceMultiplicity[dependency.size()];
		for (int i = 0; i < dependency.size(); i++) {
			multi[i] = dependency.elementAt(i);
		}
		graph.setMultiplicities(multi);

		return graph;
	}
	
	
	private Service getService(String nameService){
		
		for(Service temp : this.totalServizi){
			if(temp.getName().equals(nameService)){
				return temp;
			}
		}
		return null;
	}
	
	
	private void addMultiplicity(Service firstService,
			Vector<ServiceMultiplicity> dependencies) {
		String countError = firstService.getName()
				+ " must have 1 incoming connection";
		
		Vector<String> preCond = firstService.getPreCondizione();
		String typeError = firstService.getName() + " must connect to ";
		
		Vector<Service> listPrecondition = new Vector<Service>();
		for(String tempServ : preCond){
			typeError = typeError + tempServ + " ";
			System.out.println(firstService.getName() + " -->  PRECONDIZIONE : " + tempServ);
			Service precondService = getService(tempServ);
			if(precondService != null){
				listPrecondition.addElement(precondService);
			}
		}
		
		dependencies.addElement(new ServiceMultiplicity(false, firstService, 1,
				"1", listPrecondition,
				countError, typeError, true));
	}
	
	
	public void addNodes(Service service, mxGraph graph, Object vertex,
			Vector<ServiceMultiplicity> dependency) {
		Vector<LinkServiceNode> links = service.getOutLinks();
		for (LinkServiceNode tempLink : links) {
			if (tempLink.getToNode() instanceof Service) {
				Service outService = (Service) tempLink.getToNode();
				Object newVertex = addNode_Edge(vertex, outService, tempLink,
						graph);
				addNodes(outService, graph, newVertex, dependency);
				addMultiplicity(outService, dependency);
			} else {
				EndNode end = new EndNode();
				Object newVertex = addNode_Edge(vertex, end, tempLink, graph);
			}
		}
	}
	
	
	public Object addNodes_Edge(Object from, Object to, Object link,
			mxGraph graph) {
		Object parent = graph.getDefaultParent();

		graph.getModel().beginUpdate();
		try {
			Object v1 = graph.insertVertex(parent, null, from, 200, lastY, 180, 30);
			lastY = lastY + 80;
			Object v2 = graph.insertVertex(parent, null, to, 200, lastY, 180, 30);
			graph.insertEdge(parent, null, link, v1, v2);
			return v2;
		} finally {
			graph.getModel().endUpdate();
		}
	}
	
	
	public Object addNode_Edge(Object from, Object to, Object link,
			mxGraph graph) {
		Object parent = graph.getDefaultParent();

		graph.getModel().beginUpdate();
		try {
			lastY = lastY + 80;
			Object v2 = graph.insertVertex(parent, null, to, 200, lastY, 180, 30);
			graph.insertEdge(parent, null, link, from, v2);
			return v2;
		} finally {
			graph.getModel().endUpdate();
		}
	}
	
	
	/**
	 * A graph that creates new edges from a given template edge.
	 */
	public static class CustomGraph extends mxGraph
	{
		/**
		 * Holds the edge to be used as a template for inserting new edges.
		 */
		protected Object edgeTemplate;

		/**
		 * Custom graph that defines the alternate edge style to be used when
		 * the middle control point of edges is double clicked (flipped).
		 */
		public CustomGraph()
		{
			setAlternateEdgeStyle("edgeStyle=mxEdgeStyle.ElbowConnector;elbow=vertical");
		}

		/**
		 * Sets the edge template to be used to inserting edges.
		 */
		public void setEdgeTemplate(Object template)
		{
			edgeTemplate = template;
		}

		/**
		 * Prints out some useful information about the cell in the tooltip.
		 */
		public String getToolTipForCell(Object cell)
		{
			String tip = "<html>";
			mxGeometry geo = getModel().getGeometry(cell);
			mxCellState state = getView().getState(cell);
			mxPoint trans = getView().getTranslate();
			
			mxCell mCell = (mxCell)cell;
			
			if(mCell.isEdge()){
//				tip += "<br>data = "+ ((Element)((mxCell)(mCell.getTarget())).getValue()).getAttribute("input");
//				tip += "</html>";
			}
			else
			{
				
			}
			return tip;
		}
		
		
		@Override
		public boolean isCellLocked(Object cell) {
			if(cell != null){
	            if(cell instanceof mxCell){
	               mxCell myCell = (mxCell)cell;
	               if(myCell.isEdge()){
	            	   if(myCell.getTarget()!=null) return true;
	               }
	            }
	         }
			return super.isCellLocked(cell);
		}
		
		@Override
		public boolean isCellEditable(Object cell) {
			if(cell != null){
	            if(cell instanceof mxCell){
	               mxCell myCell = (mxCell)cell;
	               if(!myCell.isEdge()){
	            	   return false;
	               }
	            }
	         }
			return super.isCellEditable(cell);
		}
		
//		@Override
//		public boolean isCellSelectable(Object cell) {
//			if(cell != null){
//	            if(cell instanceof mxCell){
//	               mxCell myCell = (mxCell)cell;
//	               if(myCell.isEdge()){
//	            	   return false;
//	               }
//	            }
//	         }
//			return super.isCellSelectable(cell);
//		}
		
		
		/**
		 * Overrides the method to use the currently selected edge template for new edges.
		 * 
		 * @param graph
		 * @param parent
		 * @param id
		 * @param value
		 * @param source
		 * @param target
		 * @param style
		 * @return
		 */
		public Object createEdge(Object parent, String id, Object value,
				Object source, Object target, String style)
		{
			if (edgeTemplate != null)
			{
				mxCell edge = (mxCell) cloneCells(new Object[] { edgeTemplate })[0];
				edge.setId(id);
				
				return edge;
			}

			return super.createEdge(parent, id, value, source, target, style);
		}

	}

	/**
	 * 
	 * @param args
	 */
	public static void main(String[] args)
	{
		try
		{
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		}
		catch (Exception e1)
		{
			e1.printStackTrace();
		}

//		mxConstants.SHADOW_COLOR = Color.LIGHT_GRAY;
		mxConstants.W3C_SHADOWCOLOR = "#D3D3F4";
		
		WorkflowEditor editor = new WorkflowEditor();
		
		editor.createFrame(new EditorMenuBar(editor)).setVisible(true);
	}


	public void startOrchestrator(String pathOMFile) {
		System.out.println(pathOMFile);
		this.totalServizi = this.orchestrator.getAllServicesNoStartEnd();
		this.workflow = this.orchestrator.createProcess();
		viewWorkflow();
		graph.getModel().addListener(mxEvent.CHANGE, new mxIEventListener() {
			public void invoke(Object sender, mxEventObject evt) {
				graphComponent.validateGraph();
			}
		});
		
		// Initial validation
		graphComponent.setPageVisible(false);
		graphComponent.validateGraph();
	}

}
